<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D 轨迹可视化系统</title>
    <!-- 从 CDN 引入 Three.js 库 -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            margin: 0; 
            overflow: hidden; 
            background-color: #050a18; 
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            touch-action: none;  /* 禁用浏览器默认触摸行为 */
        }
        canvas { display: block; }
        #container { width: 100vw; height: 100vh; }
        #info {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            color: rgba(255, 255, 255, 0.7);
            font-size: 14px;
            pointer-events: none; /* 允许鼠标事件穿透到 canvas */
            transition: opacity 0.3s ease;
        }
        #info.fade-out {
            opacity: 0;
        }

        /* 顶部状态栏 */
        #status-bar {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            padding: 12px 20px;
            background: rgba(10, 18, 38, 0.85);
            backdrop-filter: blur(8px);
            color: #fff;
            font-size: 15px;
            z-index: 1000;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        }
        .status-indicator {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
        }
        .status-normal .status-dot { background-color: #4CAF50; }
        .status-warning .status-dot { background-color: #FFC107; }
        .status-error .status-dot { background-color: #F44336; }
        #collapse-btn {
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: #fff;
            font-size: 18px;
            cursor: pointer;
            padding: 6px 12px;
            border-radius: 4px;
            transition: all 0.2s ease;
        }
        #collapse-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        /* 可折叠控制面板 */
        #controls {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(10, 18, 38, 0.9);
            backdrop-filter: blur(10px);
            color: #fff;
            padding: 20px;
            border-radius: 16px;
            z-index: 999;
            width: 90%;
            max-width: 400px;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
            opacity: 0;
            pointer-events: none;
            transform: translateX(-50%) translateY(20px);
        }
        #controls.expanded {
            opacity: 1;
            pointer-events: auto;
            transform: translateX(-50%) translateY(0);
        }
        #controls h3 {
            margin-bottom: 16px;
            color: #4CAF50;
            font-size: 18px;
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        .control-group {
            margin-bottom: 20px;
            padding-bottom: 16px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }
        .control-group:last-child {
            border-bottom: none;
            margin-bottom: 0;
            padding-bottom: 0;
        }
        .control-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin: 10px 0;
            font-size: 14px;
        }
        .control-item span:first-child {
            color: #ccc;
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .control-item span:first-child i {
            color: rgba(76, 175, 80, 0.7);
            width: 16px;
        }
        .control-item span:last-child {
            font-weight: 500;
            color: #fff;
        }

        /* 控制面板标题栏 */
        .control-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
        }
        .control-header h3 {
            margin: 0;
        }
        .close-btn {
            background: none;
            border: none;
            color: rgba(255, 255, 255, 0.6);
            cursor: pointer;
            font-size: 16px;
            padding: 2px;
        }
        .close-btn:hover {
            color: #fff;
        }

        /* 按钮样式 */
        .btn-group {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
            margin-top: 16px;
        }
        button {
            padding: 14px;
            border: none;
            border-radius: 8px;
            background: #4CAF50;
            color: #fff;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            touch-action: manipulation;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        button:hover {
            background: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }
        button:active {
            transform: translateY(0);
        }
        button:disabled {
            background: #333;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }
        button.secondary {
            background: rgba(255, 255, 255, 0.15);
        }
        button.secondary:hover {
            background: rgba(255, 255, 255, 0.25);
        }

        /* 移动端快捷操作按钮 */
        .mobile-shortcuts {
            position: absolute;
            top: 50%;
            right: 16px;
            transform: translateY(-50%);
            z-index: 998;
            display: flex;
            flex-direction: column;
            gap: 12px;
        }
        .shortcut-btn {
            width: 56px;
            height: 56px;
            border-radius: 50%;
            background: rgba(10, 18, 38, 0.85);
            backdrop-filter: blur(8px);
            color: #fff;
            border: 2px solid rgba(76, 175, 80, 0.5);
            font-size: 22px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            touch-action: manipulation;
            transition: all 0.2s ease;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        }
        .shortcut-btn:hover, .shortcut-btn:active {
            background: #4CAF50;
            transform: translateY(-2px);
            box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
            border-color: #4CAF50;
        }

        /* 横屏提示 */
        #orientation-tip {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(10, 18, 38, 0.95);
            backdrop-filter: blur(10px);
            color: #fff;
            padding: 24px;
            border-radius: 16px;
            font-size: 18px;
            text-align: center;
            z-index: 2000;
            display: none;
            max-width: 80%;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
        }
        #orientation-tip i {
            font-size: 48px;
            margin-bottom: 16px;
            color: #4CAF50;
        }
        @media (max-aspect-ratio: 1/1) {
            #orientation-tip {
                display: block;
            }
        }

        /* 轨迹样式选择器 */
        .style-controls {
            display: flex;
            flex-direction: column;
            gap: 12px;
            margin-top: 16px;
        }
        .color-options {
            display: flex;
            gap: 8px;
            margin-top: 8px;
        }
        .color-option {
            width: 28px;
            height: 28px;
            border-radius: 50%;
            cursor: pointer;
            transition: transform 0.2s;
            border: 2px solid transparent;
        }
        .color-option:hover {
            transform: scale(1.1);
        }
        .color-option.selected {
            border-color: white;
            box-shadow: 0 0 0 2px #4CAF50;
        }
        .slider-control {
            width: 100%;
            margin-top: 8px;
        }
        .slider-control input {
            width: 100%;
            accent-color: #4CAF50;
        }

        /* 加载动画 */
        .loader {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 1500;
            display: none;
        }
        .loader.active {
            display: block;
        }
        .spinner {
            width: 50px;
            height: 50px;
            border: 5px solid rgba(255, 255, 255, 0.1);
            border-top: 5px solid #4CAF50;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        /* 数据点信息提示 */
        #point-info {
            position: absolute;
            background: rgba(10, 18, 38, 0.85);
            backdrop-filter: blur(8px);
            color: #fff;
            padding: 12px 16px;
            border-radius: 8px;
            font-size: 14px;
            z-index: 997;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.2s;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        }
        #point-info.visible {
            opacity: 1;
        }

        /* 坐标系指示器 */
        #coordinate-system {
            position: absolute;
            bottom: 20px;
            left: 20px;
            width: 80px;
            height: 80px;
            z-index: 996;
        }

        /* 开关样式 */
        .switch {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 24px;
        }
        .switch input { opacity: 0; width: 0; height: 0; }
        .slider {
            position: absolute;
            cursor: pointer;
            top: 0; left: 0; right: 0; bottom: 0;
            background-color: #333;
            transition: .4s;
            border-radius: 24px;
        }
        .slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 16px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }
        input:checked + .slider { background-color: #4CAF50; }
        input:checked + .slider:before { transform: translateX(16px); }
    </style>
</head>
<body>
    <div id="info">连接到 WebSocket... 鼠标拖动旋转视角，滚轮缩放</div>
    <!-- 加载动画 -->
    <div class="loader" id="loader">
        <div class="spinner"></div>
    </div>
    <!-- 顶部状态栏 -->
    <div id="status-bar">
        <div class="status-indicator status-warning" id="status-container">
            <div class="status-dot"></div>
            <span id="status-text">连接中...</span>
        </div>
        <button id="collapse-btn"><i class="fas fa-sliders-h"></i></button>
    </div>
    <!-- 可折叠控制面板 -->
    <div id="controls">
        <div class="control-header">
            <h3><i class="fas fa-chart-line"></i> 3D 轨迹监控</h3>
            <button class="close-btn" id="close-controls"><i class="fas fa-times"></i></button>
        </div>
        <div class="control-group">
            <div class="control-item">
                <span><i class="fas fa-id-card"></i> 连接ID:</span>
                <span id="connId">--</span>
            </div>
            <div class="control-item">
                <span><i class="fas fa-project-diagram"></i> 轨迹数量:</span>
                <span id="traceCount">0</span>
            </div>
            <div class="control-item">
                <span><i class="fas fa-dot-circle"></i> 总点数:</span>
                <span id="pointCount">0</span>
            </div>
            <div class="control-item">
                <span><i class="fas fa-tachometer-alt"></i> 更新频率:</span>
                <span id="updateFreq">0</span> Hz
            </div>
            <div class="control-item">
                <span><i class="fas fa-clock"></i> 运行时间:</span>
                <span id="runTime">00:00:00</span>
            </div>
            <div class="control-item">
                <span><i class="fas fa-map"></i> 显示XY平面:</span>
                <label class="switch">
                    <input type="checkbox" id="showMap" checked>
                    <span class="slider round"></span>
                </label>
            </div>
        </div>
        
        <div class="control-group">
            <h4 style="margin-bottom: 10px; color: #bbb;"><i class="fas fa-paint-brush"></i> 轨迹样式</h4>
            <div class="control-item">
                <span><i class="fas fa-palette"></i> 轨迹颜色:</span>
                <div class="color-options">
                    <div class="color-option selected" style="background-color: #00ffff;" data-color="0x00ffff"></div>
                    <div class="color-option" style="background-color: #ff0000;" data-color="0xff0000"></div>
                    <div class="color-option" style="background-color: #00ff00;" data-color="0x00ff00"></div>
                    <div class="color-option" style="background-color: #ffff00;" data-color="0xffff00"></div>
                    <div class="color-option" style="background-color: #ff00ff;" data-color="0xff00ff"></div>
                </div>
            </div>
            <div class="control-item">
                <span><i class="fas fa-minus"></i> 线宽:</span>
                <span id="lineWidthValue">2</span>
            </div>
            <div class="slider-control">
                <input type="range" min="1" max="5" value="2" id="lineWidthSlider">
            </div>
            <div class="control-item">
                <span><i class="fas fa-th"></i> 点数量限制:</span>
                <span id="maxPointsValue">500</span>
            </div>
            <div class="slider-control">
                <input type="range" min="100" max="2000" step="100" value="500" id="maxPointsSlider">
            </div>
        </div>
        
        <div class="btn-group">
            <button onclick="clearAllTraces()"><i class="fas fa-trash-alt"></i> 清除轨迹</button>
            <button onclick="resetCamera()"><i class="fas fa-sync-alt"></i> 重置视角</button>
            <button onclick="reconnectWebSocket()"><i class="fas fa-redo"></i> 重新连接</button>
            <button onclick="togglePause()"><i class="fas fa-pause" id="pauseIcon"></i> <span id="pauseText">暂停</span></button>
            <button class="secondary" onclick="toggleRotationLock()"><i class="fas fa-lock-open" id="lockIcon"></i> 锁定旋转</button>
            <button class="secondary" onclick="exportData()"><i class="fas fa-download"></i> 导出数据</button>
        </div>
    </div>

    <!-- 移动端快捷操作按钮 -->
    <div class="mobile-shortcuts">
        <button class="shortcut-btn" onclick="zoomIn()" title="放大"><i class="fas fa-plus"></i></button>
        <button class="shortcut-btn" onclick="zoomOut()" title="缩小"><i class="fas fa-minus"></i></button>
        <button class="shortcut-btn" onclick="resetCamera()" title="重置"><i class="fas fa-sync-alt"></i></button>
        <button class="shortcut-btn" onclick="clearAllTraces()" title="清除"><i class="fas fa-trash-alt"></i></button>
        <button class="shortcut-btn" onclick="togglePause()" title="暂停/继续"><i class="fas fa-pause" id="mobilePauseIcon"></i></button>
    </div>
    
    <!-- 数据点信息提示 -->
    <div id="point-info"></div>
    
    <!-- 坐标系指示器 -->
    <div id="coordinate-system"></div>

    <script>
        // 全局变量
        let connId = "--";
        let traceCount = 0;
        let totalPoints = 0;
        let lastUpdateTime = 0;
        let updateInterval = 0;
        let isPaused = false;
        let isRotationLocked = false;
        let startTime = null;
        let runTimeInterval = null;
        let maxPoints = 500;
        let lineWidth = 2;
        let currentColor = 0x00ffff;
        let plane, gridHelper; // XY平面和网格线变量
        
        // 轨迹数据存储
        const trajectories = new Map(); // 多轨迹支持: id -> { points: [], line: Object, marker: Object }
        const allPoints = []; // 所有点的历史记录，用于导出
        
        // 1. 初始化 Three.js 场景
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x050a18); // 深色背景
        document.body.appendChild(renderer.domElement);

        // 添加轨道控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.enableZoom = true;
        controls.enablePan = true;
        
        // 保存初始相机位置，用于重置
        const initialCameraPosition = new THREE.Vector3(30, 30, 10);
        camera.position.copy(initialCameraPosition);

        // 2. 添加光源
        const ambientLight = new THREE.AmbientLight(0x333333); // 环境光
        scene.add(ambientLight);

        const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight1.position.set(1, 1, 1);
        scene.add(directionalLight1);

        const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
        directionalLight2.position.set(-1, -1, -1);
        scene.add(directionalLight2);

        // 添加XY平面地图作为运动区域
        const planeSize = 50; // 平面大小，可根据实际需求调整
        const planeGeometry = new THREE.PlaneGeometry(planeSize, planeSize);

        // 创建网格材质，半透明以避免遮挡轨迹
        const planeMaterial = new THREE.MeshBasicMaterial({
            color: 0x1a237e,
            transparent: true,
            opacity: 0.2,
            side: THREE.DoubleSide
        });

        // 创建平面网格
        plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.rotation.x = -Math.PI / 2; // 旋转90度使其成为XY平面
        plane.position.y = 0; // 放置在Z=0平面
        scene.add(plane);

        // 添加网格线以增强可视化效果
        gridHelper = new THREE.GridHelper(planeSize, 20, 0x303f9f, 0x1a237e);
        gridHelper.position.y = 0.01; // 稍微高于平面，避免z-fighting
        scene.add(gridHelper);

        // 添加坐标轴标签
        function addAxisLabels() {
            // X轴标签
            const xLabel = document.createElement('div');
            xLabel.style.position = 'absolute';
            xLabel.style.color = '#ff0000';
            xLabel.style.fontSize = '14px';
            xLabel.style.fontWeight = 'bold';
            xLabel.textContent = 'X';
            xLabel.style.left = `${window.innerWidth - 30}px`;
            xLabel.style.top = `${window.innerHeight / 2}px`;
            xLabel.style.zIndex = '995';
            document.body.appendChild(xLabel);
            
            // Y轴标签
            const yLabel = document.createElement('div');
            yLabel.style.position = 'absolute';
            yLabel.style.color = '#00ff00';
            yLabel.style.fontSize = '14px';
            yLabel.style.fontWeight = 'bold';
            yLabel.textContent = 'Y';
            yLabel.style.left = `${window.innerWidth / 2}px`;
            yLabel.style.top = '30px';
            yLabel.style.zIndex = '995';
            document.body.appendChild(yLabel);
        }
        addAxisLabels();

        // 3. 添加坐标系辅助线
        const axesHelper = new THREE.AxesHelper(10);
        scene.add(axesHelper);
        
        // 小坐标系指示器
        createCoordinateIndicator();

        // 4. WebSocket 连接和数据处理
        const infoDiv = document.getElementById('info');
        const statusContainer = document.getElementById('status-container');
        const statusText = document.getElementById('status-text');
        const loader = document.getElementById('loader');
        
        let ws;
        let reconnectTimeout;
        const maxReconnectAttempts = 10;
        let reconnectAttempts = 0;

        // 初始化WebSocket连接
        function initWebSocket() {
            if (ws) {
                ws.close();
            }
            
            showLoader(true);
            const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            ws = new WebSocket(`${wsProtocol}//${window.location.host}/ws`);

            ws.onopen = () => {
                console.log('WebSocket connection established');
                infoDiv.textContent = '已连接！接收数据中... 鼠标拖动旋转视角，滚轮缩放';
                statusContainer.className = 'status-indicator status-normal';
                statusText.textContent = '已连接';
                showLoader(false);
                reconnectAttempts = 0;
                
                // 开始计时
                if (!startTime) {
                    startTime = new Date();
                    runTimeInterval = setInterval(updateRunTime, 1000);
                }
            };

            ws.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    // 计算更新频率
                    const now = Date.now();
                    if (lastUpdateTime > 0) {
                        updateInterval = (updateInterval * 0.7) + (now - lastUpdateTime) * 0.3; // 平滑处理
                        document.getElementById('updateFreq').textContent = (1000 / updateInterval).toFixed(1);
                    }
                    lastUpdateTime = now;
                    
                    handleWebSocketMessage(data);
                } catch (e) {
                    console.error('Error parsing message:', e);
                    showError(`数据解析错误: ${e.message}`);
                }
            };

            ws.onclose = (event) => {
                console.log('WebSocket connection closed', event);
                infoDiv.textContent = `连接已关闭 (代码: ${event.code})。尝试重新连接...`;
                statusContainer.className = 'status-indicator status-warning';
                statusText.textContent = '已断开';
                showLoader(false);
                
                // 重连逻辑
                if (reconnectAttempts < maxReconnectAttempts) {
                    reconnectAttempts++;
                    const delay = Math.min(1000 * reconnectAttempts, 10000); // 指数退避重连
                    reconnectTimeout = setTimeout(initWebSocket, delay);
                } else {
                    statusContainer.className = 'status-indicator status-error';
                    statusText.textContent = '重连失败';
                    infoDiv.textContent = '无法建立连接，请检查服务器或点击"重新连接"按钮';
                }
            };

            ws.onerror = (error) => {
                console.error('WebSocket error:', error);
                showError(`连接错误: ${error.message || '未知错误'}`);
                statusContainer.className = 'status-indicator status-error';
                statusText.textContent = '连接错误';
                showLoader(false);
            };
        }

        // 处理WebSocket消息
        function handleWebSocketMessage(data) {
            if (isPaused) return; // 如果暂停，不处理新数据
            
            switch (data.type) {
                case "connection_ack":
                    connId = data.conn_id || "--";
                    document.getElementById('connId').textContent = connId;
                    break;                
                case "trace_data":
                    if (Array.isArray(data.data)) {
                        data.data.forEach(trace => {
                            updateTrajectory(trace);
                        });
                        // 更新轨迹数量
                        traceCount = new Set(Array.from(trajectories.keys())).size;
                        document.getElementById('traceCount').textContent = traceCount;
                    }
                    break;
                case "circle_data":
                    if (data.data && Array.isArray(data.data) && data.data.length > 0) {
                        updateTrajectory(data.data[0]);
                    }
                    break;
                case "heartbeat_ack":
                    break;
                case "reset_ack":
                    console.log(data.message);
                    break;
                default:
                    console.log("未知消息类型：", data.type);
            }
        }

        // 5. 更新轨迹的函数（支持多轨迹）
        function updateTrajectory(pointData) {
            // 为每个轨迹分配唯一ID，如果没有则使用默认ID
            const traceId = pointData.id || 'default';
            const { x, y, z, timestamp } = pointData;
            const point = new THREE.Vector3(x, z, y);
            
            // 记录所有点数据，用于导出
            allPoints.push({
                id: traceId,
                x, y, z,
            });
            
            // 更新总点数
            totalPoints++;
            document.getElementById('pointCount').textContent = totalPoints;
            
            // 如果轨迹不存在，则创建新轨迹
            if (!trajectories.has(traceId)) {
                // 创建轨迹线
                const geometry = new THREE.BufferGeometry().setFromPoints([point]);
                const material = new THREE.LineBasicMaterial({ 
                    color: currentColor, 
                    linewidth: lineWidth 
                });
                const line = new THREE.Line(geometry, material);
                
                // 创建标记点
                const markerGeometry = new THREE.SphereGeometry(0.15, 32, 32);
                const markerMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
                const marker = new THREE.Mesh(markerGeometry, markerMaterial);
                marker.position.set(point.x, point.y, point.z);
                
                // 添加到场景
                scene.add(line);
                scene.add(marker);
                
                // 存储轨迹信息
                trajectories.set(traceId, {
                    points: [point],
                    line,
                    marker,
                    material
                });
            } else {
                // 更新现有轨迹
                const trajectory = trajectories.get(traceId);
                trajectory.points.push(point);
                
                // 保持轨迹长度限制
                if (trajectory.points.length > maxPoints) {
                    trajectory.points.shift();
                }
                
                // 更新轨迹线
                trajectory.line.geometry.setFromPoints(trajectory.points);
                trajectory.line.geometry.attributes.position.needsUpdate = true;
                
                // 更新标记点位置
                trajectory.marker.position.set(point.x, point.y, point.z);
            }
        }

        // 6. 控制面板交互
        document.getElementById('collapse-btn').addEventListener('click', () => {
            document.getElementById('controls').classList.toggle('expanded');
        });
        
        document.getElementById('close-controls').addEventListener('click', () => {
            document.getElementById('controls').classList.remove('expanded');
        });
        
        // XY平面显示控制
        document.getElementById('showMap').addEventListener('change', function(e) {
            plane.visible = e.target.checked;
            gridHelper.visible = e.target.checked;
            showInfo(`XY平面已${e.target.checked ? '显示' : '隐藏'}`);
        });
        
        // 轨迹样式控制
        document.querySelectorAll('.color-option').forEach(option => {
            option.addEventListener('click', () => {
                // 更新选中状态
                document.querySelectorAll('.color-option').forEach(opt => 
                    opt.classList.remove('selected'));
                option.classList.add('selected');
                
                // 更新颜色
                currentColor = parseInt(option.getAttribute('data-color'));
                
                // 应用到所有轨迹
                trajectories.forEach(trajectory => {
                    trajectory.material.color.set(currentColor);
                });
            });
        });
        
        // 线宽控制
        const lineWidthSlider = document.getElementById('lineWidthSlider');
        const lineWidthValue = document.getElementById('lineWidthValue');
        
        lineWidthSlider.addEventListener('input', () => {
            lineWidth = parseInt(lineWidthSlider.value);
            lineWidthValue.textContent = lineWidth;
            
            // 应用到所有轨迹
            trajectories.forEach(trajectory => {
                trajectory.material.linewidth = lineWidth;
            });
        });
        
        // 最大点数量控制
        const maxPointsSlider = document.getElementById('maxPointsSlider');
        const maxPointsValue = document.getElementById('maxPointsValue');
        
        maxPointsSlider.addEventListener('input', () => {
            maxPoints = parseInt(maxPointsSlider.value);
            maxPointsValue.textContent = maxPoints;
            
            // 修剪现有轨迹点
            trajectories.forEach(trajectory => {
                while (trajectory.points.length > maxPoints) {
                    trajectory.points.shift();
                }
                trajectory.line.geometry.setFromPoints(trajectory.points);
                trajectory.line.geometry.attributes.position.needsUpdate = true;
            });
        });

        // 7. 辅助函数
        function clearAllTraces() {
            // 移除所有轨迹和标记
            trajectories.forEach((trajectory, id) => {
                scene.remove(trajectory.line);
                scene.remove(trajectory.marker);
            });
            
            // 清空数据
            trajectories.clear();
            allPoints.length = 0;
            traceCount = 0;
            totalPoints = 0;
            
            // 更新UI
            document.getElementById('traceCount').textContent = '0';
            document.getElementById('pointCount').textContent = '0';
            
            showInfo('所有轨迹已清除');
        }

        function resetCamera() {
            camera.position.copy(initialCameraPosition);
            controls.reset();
            showInfo('视角已重置');
        }

        function reconnectWebSocket() {
            if (reconnectTimeout) {
                clearTimeout(reconnectTimeout);
            }
            initWebSocket();
            showInfo('正在重新连接...');
        }

        function toggleRotationLock() {
            isRotationLocked = !isRotationLocked;
            controls.enableRotate = !isRotationLocked;
            
            const lockIcon = document.getElementById('lockIcon');
            if (isRotationLocked) {
                lockIcon.className = 'fas fa-lock';
                showInfo('旋转已锁定');
            } else {
                lockIcon.className = 'fas fa-lock-open';
                showInfo('旋转已解锁');
            }
        }

        function togglePause() {
            isPaused = !isPaused;
            const pauseIcon = document.getElementById('pauseIcon');
            const pauseText = document.getElementById('pauseText');
            const mobilePauseIcon = document.getElementById('mobilePauseIcon');
            
            if (isPaused) {
                pauseIcon.className = 'fas fa-play';
                mobilePauseIcon.className = 'fas fa-play';
                pauseText.textContent = '继续';
                showInfo('数据接收已暂停');
            } else {
                pauseIcon.className = 'fas fa-pause';
                mobilePauseIcon.className = 'fas fa-pause';
                pauseText.textContent = '暂停';
                showInfo('数据接收已继续');
            }
        }

        function zoomIn() {
            camera.position.z = Math.max(1, camera.position.z - 5);
            showInfo('已放大');
        }

        function zoomOut() {
            camera.position.z = Math.min(200, camera.position.z + 5);
            showInfo('已缩小');
        }

        function exportData() {
            if (allPoints.length === 0) {
                showInfo('没有数据可导出');
                return;
            }
            
            try {
                const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(allPoints, null, 2));
                const downloadAnchorNode = document.createElement('a');
                downloadAnchorNode.setAttribute("href", dataStr);
                downloadAnchorNode.setAttribute("download", `trajectory_data_${new Date().toISOString().replace(/:/g, '-')}.json`);
                document.body.appendChild(downloadAnchorNode);
                downloadAnchorNode.click();
                downloadAnchorNode.remove();
                showInfo('数据已导出');
            } catch (e) {
                showError(`导出失败: ${e.message}`);
            }
        }

        // 显示信息提示
        function showInfo(message) {
            const infoDiv = document.getElementById('info');
            infoDiv.textContent = message;
            infoDiv.classList.remove('fade-out');
            
            setTimeout(() => {
                infoDiv.classList.add('fade-out');
            }, 3000);
        }

        // 显示错误提示
        function showError(message) {
            const infoDiv = document.getElementById('info');
            infoDiv.textContent = `错误: ${message}`;
            infoDiv.style.color = '#F44336';
            infoDiv.classList.remove('fade-out');
            
            setTimeout(() => {
                infoDiv.style.color = 'rgba(255, 255, 255, 0.7)';
                infoDiv.classList.add('fade-out');
            }, 5000);
        }

        // 显示/隐藏加载动画
        function showLoader(show) {
            if (show) {
                loader.classList.add('active');
            } else {
                loader.classList.remove('active');
            }
        }

        // 更新运行时间
        function updateRunTime() {
            if (!startTime) return;
            
            const now = new Date();
            const elapsedMs = now - startTime;
            const seconds = Math.floor((elapsedMs / 1000) % 60);
            const minutes = Math.floor((elapsedMs / (1000 * 60)) % 60);
            const hours = Math.floor(elapsedMs / (1000 * 60 * 60));
            
            document.getElementById('runTime').textContent = 
                `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        }

        // 创建小坐标系指示器
        function createCoordinateIndicator() {
            const container = document.getElementById('coordinate-system');
            const canvas = document.createElement('canvas');
            canvas.width = 80;
            canvas.height = 80;
            container.appendChild(canvas);
            
            const ctx = canvas.getContext('2d');
            
            // 绘制坐标系 (修正为XY平面显示)
            function drawCoordinates() {
                ctx.clearRect(0, 0, 80, 80);
                
                // 背景
                ctx.fillStyle = 'rgba(10, 18, 38, 0.6)';
                ctx.beginPath();
                ctx.roundRect(0, 0, 80, 80, 8);
                ctx.fill();
                
                // 原点
                const originX = 20;
                const originY = 60;
                
                // X轴 (红色) - 水平向右
                ctx.strokeStyle = '#ff0000';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(originX, originY);
                ctx.lineTo(originX + 30, originY);
                ctx.stroke();
                
                // Y轴 (绿色) - 水平向前
                ctx.strokeStyle = '#00ff00';
                ctx.beginPath();
                ctx.moveTo(originX, originY);
                ctx.lineTo(originX + 20, originY - 20);
                ctx.stroke();
                
                // Z轴 (蓝色) - 垂直向上
                ctx.strokeStyle = '#0000ff';
                ctx.beginPath();
                ctx.moveTo(originX, originY);
                ctx.lineTo(originX, originY - 30);
                ctx.stroke();
                
                // 轴标签
                ctx.fillStyle = '#fff';
                ctx.font = '12px Arial';
                ctx.fillText('X', originX + 32, originY + 4);
                ctx.fillText('Y', originX + 22, originY - 24);
                ctx.fillText('Z', originX - 6, originY - 32);
            }
            
            drawCoordinates();
        }
        // 8. 窗口大小变化处理
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // 9. 鼠标悬停显示点信息
        // 添加射线检测，用于显示鼠标指向的点信息
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();
        
        window.addEventListener('mousemove', (event) => {
            // 计算鼠标在标准化设备坐标中的位置 (-1 到 1)
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
            
            // 更新射线投射器
            raycaster.setFromCamera(mouse, camera);
            
            // 检测与轨迹的交点
            const lineObjects = Array.from(trajectories.values()).map(t => t.line);
            const intersects = raycaster.intersectObjects(lineObjects, false);
            
            const pointInfo = document.getElementById('point-info');
            if (intersects.length > 0) {
                // 找到最近的交点
                const closest = intersects[0];
                const point = closest.point;
                
                // 显示点信息
                pointInfo.innerHTML = `
                    X: ${point.x.toFixed(2)}<br>
                    Y: ${point.y.toFixed(2)}<br>
                    Z: ${point.z.toFixed(2)}
                `;
                pointInfo.style.left = `${event.clientX + 10}px`;
                pointInfo.style.top = `${event.clientY + 10}px`;
                pointInfo.classList.add('visible');
            } else {
                pointInfo.classList.remove('visible');
            }
        });

        // 10. 动画循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }

        // 初始化
        initWebSocket();
        animate();
    </script>
</body>
</html>